using System;
using System.IO;
using System.Data;
using System.Collections;
using System.Text;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Soap;
using System.Security.Permissions;

using Widgetsphere.Core.Util;

namespace Widgetsphere.Core.DataAccess
{
  [Serializable]
  public abstract partial class SubDomainBase : DataSet, ISerializable
  {
    #region Member Variables

		[Flags]
		public enum ItemStateConstants
		{
			Detached = 1,
			Unchanged = 2,
			Added = 4,
			Deleted = 8,
			Modified = 16,
		}

    protected string mModifier;
    protected ArrayList mSelectCommands;
    // Our webservice reference object, don't serialize this!!
    protected DataAccessService mDataAccessService = null;
    private string _identifier = Guid.NewGuid().ToString();
    private int _timeOut = 0;

    #endregion

    #region Properties

    public string Identifier
    {
      get { return _identifier; }
    }

		public virtual string Modifier
		{
			get { return this.mModifier; }
			set { this.mModifier = value; }
		}

		public virtual int TimeOut
    {
      get { return _timeOut; }
      set
      {
        if (value < 0)
          value = 0;
        _timeOut = value;
      }
    }

    #endregion

    #region Constructors
    /// <summary>
    /// Use this to run SelectCommands locally.
    /// </summary>
    public SubDomainBase(string modifier)
    {
      mModifier = modifier;
      mSelectCommands = new ArrayList();
      if (!this.DirectConnect)
      {
        mDataAccessService = new DataAccessService();
        mDataAccessService.Url = this.DataAccessServiceUrl;
      }
      this.EnforceConstraints = true;
    }

    public SubDomainBase(DataSet dataset, string modifier)
      : this(modifier)
    {
      this.Merge(dataset, true, MissingSchemaAction.AddWithKey);
    }

    protected SubDomainBase()
    {
      this.EnforceConstraints = true;
    }

    #endregion

    #region Abstract Methods
		[Obsolete("This property no longer applies.")]
    protected abstract bool DirectConnect { get;}
		[Obsolete("This property no longer applies.")]
    protected abstract string DataAccessServiceUrl { get;}
    protected abstract void CreateStronglyTypedTables(DataSet ds);
    public abstract string ConnectionString { get;}
    protected internal abstract IBusinessCollection this[Enum collection] { get;}
    #endregion

    #region ISerializable members
    /// <summary>
    /// Protected constructor for support of custom Deserialization.
    /// </summary>
    /// <param name="info"></param>
    /// <param name="context"></param>
    /// 
    protected SubDomainBase(SerializationInfo info, StreamingContext context)
    {
      if (!this.DirectConnect)
      {
        mDataAccessService = new DataAccessService();
        mDataAccessService.Url = this.DataAccessServiceUrl;
      }

      this.EnforceConstraints = false;
      //Deserialize DataSet
      DataSet ds = new DataSet();

      string xmlSchema = info.GetString("XmlSchema");
      using (StringReader sr = new StringReader(xmlSchema))
      {
        ds.ReadXmlSchema(sr);
      }
      string xmlData = info.GetString("DataSet");
      using (MemoryStream ms = StringHelper.StringToMemoryStream(xmlData))
      {
        ms.Position = 0;
        ds.ReadXml(ms, XmlReadMode.DiffGram);
      }
      ArrayList persistableEntities = (ArrayList)SerializeHelper.SoapDeserializeFromString(info.GetString("persistableEntities"));
      foreach (PersistableDomainCollectionBase pe in persistableEntities)
      {
        this.AddCollection(pe);
      }
      this.Merge(ds, false, System.Data.MissingSchemaAction.Error);

      //Deserialize Modifier
      mModifier = info.GetString("mModifier");

      //Deserialize Retrieve Rules
      mSelectCommands = (ArrayList)info.GetValue("mSelectCommands", typeof(ArrayList));

      //Ensure constraints enforced
    }

    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = false)]
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
      //Serialize DataSet
      info.AddValue("XmlSchema", this.GetXmlSchema());
      using (MemoryStream ms = new MemoryStream())
      {
        this.WriteXml(ms, XmlWriteMode.DiffGram);
        string xmlDataset = StringHelper.MemoryStreamToString(ms);
        //info.AddValue("DataSet", @"<![CDATA[<" + xmlDataset + @"]]>");
        info.AddValue("DataSet", xmlDataset);
      }
      ArrayList persistableEntities = new ArrayList();
      foreach (PersistableDomainCollectionBase pe in this.Tables)
      {
        persistableEntities.Add(pe);
      }
      info.AddValue("persistableEntities", SerializeHelper.SoapSerialize(persistableEntities));
      info.AddValue("enforceConstraints", this.EnforceConstraints);

      //Serialize Modifier
      info.AddValue("mModifier", this.mModifier);

      //Serialize Retrieve Rules
      info.AddValue("mSelectCommands", mSelectCommands);
    }
    #endregion

    #region DomainCollectionBase

    protected DomainCollectionBase this[string requestedCollection]
    {
      get
      {
        DataTable dt = this.Tables[requestedCollection.ToString()];
        return (DomainCollectionBase)dt;
      }
    }

    #endregion

    #region ClearSelectCommands

    /// <summary>
    /// Remove all SelectCommands from internal collection.
    /// </summary>
		public virtual void ClearSelectCommands()
    {
      mSelectCommands.Clear();
    }

    #endregion

    #region AddSelectCommand

    /// <summary>
    /// Add a SelectCommand to the internal collection.
    /// </summary>
    /// <param name="rule"></param>
		public virtual void AddSelectCommand(SelectCommand command)
    {
      if (mSelectCommands == null)
        mSelectCommands = new ArrayList();
      mSelectCommands.Add(command);
    }

    #endregion

    #region Retrieve

    private bool mDisableRetrieve = false;

		public virtual bool DisableAutomaticRetrieves
    {
      get
      {
        return mDisableRetrieve;
      }
      set
      {
        mDisableRetrieve = value;
      }
    }
    /// <summary>
    /// Executes fill rules to produce a result
    /// </summary>
		public virtual void RunSelectCommands()
    {
      if (!mDisableRetrieve)
      {
        if (mDataAccessService == null)
        {
          RunRules();
        }
        else
        {
          System.Diagnostics.Debug.WriteLine("Go Through Web Service");
          try
          {
            bool oldEc = this.EnforceConstraints;
            this.EnforceConstraints = false;
            SubDomainWrapper sdWrapper = mDataAccessService.Retrieve(new SubDomainWrapper(this));
            this.MergeNewDomain(sdWrapper.SubDomain);
            this.EnsureParentRowsExist();
            this.mSelectCommands.Clear();
            this.EnforceConstraints = oldEc;
          }
          catch (Exception ex)
          {
            System.Diagnostics.Debug.WriteLine(ex.ToString());
            throw;
          }
        }
      }
    }

    private void MergeNewDomain(DataSet ds)
    {
      this.CreateStronglyTypedTables(ds);
      this.Merge(ds, false, System.Data.MissingSchemaAction.Error);
    }
    #endregion

    #region RunRules

    private void RunRules()
    {
      try
      {
        bool oldEc = this.EnforceConstraints;
        this.EnforceConstraints = false;
        foreach (SelectCommand rule in mSelectCommands)
        {
          this.ExecuteRule(rule);
        }
        this.ClearSelectCommands();
        EnsureParentRowsExist();
        this.EnforceConstraints = oldEc;
      }
      catch (Exception ex)
      {
        //TODO: Make this fool proof giving good details.
				if (ex.Message.Contains("timeout period elapsed"))
					throw new Exception("The timeout period elapsed prior to completion of the operation or the server is not responding (Timeout = " + this.TimeOut + ").");
				else
					throw ex;
			}
      finally
      {
        this.ClearSelectCommands();
      }
    }

    #endregion

    #region ExecuteRule

    private void ExecuteRule(SelectCommand rule)
    {
      rule.TimeOut = rule.DefaultTimeOut;
			if (this.TimeOut > 0) rule.TimeOut = this.TimeOut;

      IDomainCollection domainCollection = rule.Execute(this);
      if (typeof(ReadOnlyDomainCollection).IsInstanceOfType(domainCollection))
      {
        this.AddCollection((ReadOnlyDomainCollection)domainCollection);
      }
      else
      {
        this.AddCollection((PersistableDomainCollectionBase)domainCollection);
      }
    }

    #endregion

    #region MergeDomainCollection

    private void MergeDomainCollection(DomainCollectionBase newDomainCollection)
    {
      DomainCollectionBase currentDomainCollection = this[""];
      this.Merge(newDomainCollection, false, MissingSchemaAction.Error);
    }

    #endregion

    #region AddCollection

    protected void AddCollection(PersistableDomainCollectionBase collection)
    {
      if (this.Tables.Contains(collection.TableName))
      {
        this.MergeDomainCollection(collection);				
      }
      else
      {
        this.Tables.Add(collection);
        collection.CreateRelations();
      }

			//Ensure that all relations are there
			foreach (PersistableDomainCollectionBase c in this.Tables)
			{
			  c.CreateRelations();
			}

      if (collection.Rows.Count > 0)
      {
        ((PersistableDomainCollectionBase)this.Tables[collection.TableName]).SetChildSelectedFalse();
      }

    }

    protected void AddCollection(ReadOnlyDomainCollection collection)
    {
      if (this.Tables.Contains(collection.TableName))
      {
        this.MergeDomainCollection(collection);
      }
      else
      {
        this.Tables.Add(collection);
      }
    }

    #endregion

    #region EnsureParentRowsExist
    protected bool EnsureParentExistsOn = true;

		public virtual void EnsureParentRowsExist()
    {
      if (EnsureParentExistsOn)
      {
        bool oldEc = this.EnforceConstraints;
        try
        {
          this.EnforceConstraints = true;
        }
        catch (ConstraintException ex)
        {
          if (this.HasErrors)
          {
            this.HandleEnsureParentExistException(ex);
          }
          else
          {
            throw;
          }
        }
        finally
        {
          this.EnforceConstraints = oldEc;
        }
      }
    }

    #endregion

    #region HandleEnsureParentExistException

		private void HandleEnsureParentExistException(ConstraintException ex)
		{
			try
			{
				bool errorsHandled = false;
				foreach (IDomainCollection idc in this.Tables)
				{
					if (idc != null)
					{
						if (typeof(PersistableDomainCollectionBase).IsInstanceOfType(idc))
						{
							PersistableDomainCollectionBase pe = (PersistableDomainCollectionBase)idc;
							if ((pe != null) && pe.HasErrors)
							{
								if (pe.HandleErrors())
								{
									errorsHandled = true;
								}
							}
						}
					}
				}
				if (errorsHandled)
				{
					this.RunSelectCommands();
				}
				else
				{
					throw ex;
				}
			}
			catch (Exception ex1)
			{
				throw ex1;
			}
		}

    #endregion

    #region RunRulesFromWebService

    /// <summary>
    /// Called from webservice webmethod to run SelectCommands and return the results.
    /// </summary>
    /// <returns></returns>
		public virtual SubDomainBase RunRulesFromWebService()
    {
      this.RunRules();
      return this;
    }

    #endregion

    #region RunPersist

    private void RunPersist()
    {
      if (this.Tables.Count > 0)
      {
        ((PersistableDomainCollectionBase)this.Tables[0]).FullUpdate();
      }
    }

    #endregion

    #region Persist

		public virtual void Persist()
    {
      if (mDataAccessService == null)
      {
        RunPersist();
      }
      else
      {
        System.Diagnostics.Debug.WriteLine("Go Through Web Service");
        try
        {
          bool oldEc = this.EnforceConstraints;
          this.EnforceConstraints = false;
          SubDomainWrapper sdWrapper = mDataAccessService.Persist(new SubDomainWrapper(this));
          //this.Clear();
          this.AcceptChanges();
          this.MergeNewDomain(sdWrapper.SubDomain);
          this.EnforceConstraints = oldEc;
        }
        catch (Exception ex)
        {
          System.Diagnostics.Debug.WriteLine(ex.ToString());
          throw;
        }
      }
    }

    #endregion

    #region File Operations

		public virtual void SaveToFile(string file)
    {
      SaveToFile(file, true);
    }

		public virtual void SaveToFile(string file, bool overwrite)
    {
      if (File.Exists(file) && overwrite)
      {
        File.Delete(file);
      }
      else if (File.Exists(file))
      {
        throw new Widgetsphere.Core.Exceptions.WSException("File already exists");
      }
      SerializeHelper.SoapSerialize(file, this);
    }

		public static SubDomainBase LoadFromFile(string file)
    {
      if (File.Exists(file))
      {
        return (SubDomainBase)SerializeHelper.SoapDeserialize(file);
      }
      else
      {
        throw new Widgetsphere.Core.Exceptions.WSException("File does not exist");
      }
    }

    #endregion

    #region IsDirty

		public virtual bool IsDirty
    {
      get { return this.HasChanges(); }
    }

    #endregion

    #region Set Operations

    private DataTable SetupSetOperationDataSet(DataSet dataset, string key)
    {
      //There must be a table
      if (dataset.Tables.Count != 1)
        throw new ApplicationException("There must be one datatable present to perform this operation.");

      //The Key must exist
      DataTable dataTable = dataset.Tables[0];
      if (!dataTable.Columns.Contains(key))
        throw new ApplicationException("There specified column does not exist in the table.");

      //If there is no primary key on the table then create one
      if (dataTable.PrimaryKey.Length == 0)
      {
        DataColumn[] primaryKeys = { dataTable.Columns[key] };
        dataTable.PrimaryKey = primaryKeys;
      }
      else
      {
        //If the primary key is not the specified key then drop 
        //it and create a new primary key as the specified key
        if (!Widgetsphere.Core.Util.StringHelper.Match(dataTable.PrimaryKey[0].ColumnName, key, true))
        {
          DataColumn[] primaryKeys = { dataTable.Columns[key] };
          dataTable.PrimaryKey = primaryKeys;
        }
      }

      return dataTable;
    }

		public virtual DataSet Union(DataSet dataset, string key)
    {
      //Create a temp dataset
      DataSet retval = new DataSet();
      retval.Merge(this, true, MissingSchemaAction.AddWithKey);

      //Get the 2 datatables
      DataTable dataTable1 = SetupSetOperationDataSet(retval, key);
      DataTable dataTable2 = SetupSetOperationDataSet(dataset, key);

      retval.Merge(dataset, true, MissingSchemaAction.AddWithKey);
      ResetRank(retval);

      return retval;
    }

		public virtual DataSet Intersection(DataSet dataset, string key)
    {
      //Create a temp dataset
      DataSet retval = new DataSet();
      retval.Merge(this, true, MissingSchemaAction.AddWithKey);

      //Get the 2 datatables
      DataTable dataTable1 = SetupSetOperationDataSet(retval, key);
      DataTable dataTable2 = SetupSetOperationDataSet(dataset, key);

      for (int ii = dataTable2.Rows.Count - 1; ii >= 0; ii--)
      {
        //Current row
        DataRow dr = dataTable2.Rows[ii];

        bool hasMatch = false;
        if ((dataTable1.Columns[key].DataType == typeof(System.String)) || (dataTable1.Columns[key].DataType == typeof(System.Guid)) || (dataTable1.Columns[key].DataType == typeof(System.DateTime)))
          hasMatch = (dataTable1.Select(key + " = '" + (string)dr[key] + "'").Length > 0);
        else
          hasMatch = (dataTable1.Select(key + " = " + (string)dr[key]).Length > 0);

        //If there is no match then remove it since there is no intersection
        if (!hasMatch)
          dataTable1.Rows.Remove(dr);
      }
      ResetRank(retval);

      return retval;
    }

		public virtual DataSet Subtract(DataSet dataset, string key)
    {
      //Create a temp dataset
      DataSet retval = new DataSet();
      retval.Merge(this, true, MissingSchemaAction.AddWithKey);

      //Get the 2 datatables
      DataTable dataTable1 = SetupSetOperationDataSet(retval, key);
      DataTable dataTable2 = SetupSetOperationDataSet(dataset, key);

      for (int ii = dataTable2.Rows.Count - 1; ii >= 0; ii--)
      {
        //Current row
        DataRow dr = dataTable2.Rows[ii];

        DataRow[] rows;
        if ((dataTable1.Columns[key].DataType == typeof(System.String)) || (dataTable1.Columns[key].DataType == typeof(System.DateTime)) || (dataTable1.Columns[key].DataType == typeof(System.Guid)))
          rows = dataTable1.Select(key + " = '" + dr[key].ToString() + "'");
        else
          rows = dataTable1.Select(key + " = " + dr[key].ToString());

        //If there is a match then remove it for subtraction
        foreach (DataRow loopRow in rows)
          dataTable1.Rows.Remove(loopRow);
      }
      ResetRank(retval);


      return retval;
    }

    private void ResetRank(DataSet ds)
    {
      //Reset the RankOrder in this new dataset
      int ii = 1;
      foreach (DataRow dr in ds.Tables[0].Rows)
      {
        dr["rankorder"] = ii;
        ii++;
      }
    }

    #endregion

  }
}
